﻿// Copyright (c) 2008, Colin Burn
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 
// * Neither the name of WTCL nor the names of its contributors may be
// used to endorse or promote products derived from this software
// without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

using WTCL.Charts.Axis.Data;
using WTCL.Charts.Axis.Ticks;
using WTCL.Data.Series;
using WTCL.Data.Series.Interface;
using WTCL.Data.Type;

namespace WTCL.Charts.Axis.Scale
{
    internal sealed class DoubleXAxisScale<TSeries, TData, TChart, TChartCanvas> : AxisScale<TChart, ValueAxisData<double, double>, XAxisTick>
        where TSeries : SeriesBase<TData>, ISampleXAxisSeries<IDataType<double, double>>
        where TChart : Chart<TSeries, TData, TChart, TChartCanvas>
        where TChartCanvas : BaseChartCanvas<TSeries, TData, TChart, TChartCanvas>
    {
        private List<double> spacings = new List<double>();

        protected override List<List<XAxisTick>> UpdateTickMarks()
        {
            List<List<XAxisTick>> ret = new List<List<XAxisTick>>();

            Typeface typeface = new Typeface(this.Chart.ChartCanvas.FontFamily, this.Chart.ChartCanvas.FontStyle, this.Chart.ChartCanvas.FontWeight, this.Chart.ChartCanvas.FontStretch);

            for (int i = 0; i < base.AxisData.Count; ++i)
            {
                ValueAxisData<double, double> minMaxData = base.AxisData[i];

                List<XAxisTick> tickData = new List<XAxisTick>();

                if (minMaxData != null)
                {
                    if (minMaxData.ActualMinValue != null && minMaxData.ActualMaxValue != null)
                    {
                        for (IDataType<double, double> value = minMaxData.ActualMinValue; value.LessThanOrEqual(minMaxData.ActualMaxValue); value = value.Add(this.spacings[i]))
                        {
                            FormattedText label = new FormattedText(FormatItem(value), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, this.Chart.ChartCanvas.FontSize, this.Brush);
                            double ratio = value.GetRatio(minMaxData.ActualMinValue, minMaxData.ActualMaxValue);

                            double location = (this.Chart.ChartCanvas.ActualWidth * ratio);

                            tickData.Add(new XAxisTick(label, location));
                        }
                    }
                }

                ret.Add(tickData);
            }

            return ret;
        }

        protected override List<ValueAxisData<double, double>> UpdateAxisData()
        {
            this.spacings.Clear();

            List<ValueAxisData<double, double>> ret = new List<ValueAxisData<double, double>>();

            for (int seriesIndex = 0; seriesIndex < this.Chart.Series.Count; ++seriesIndex)
            {
                double xSpacing = CalculateAxisSpacing(this.Chart.Series[seriesIndex].ActualXMinimum, this.Chart.Series[seriesIndex].ActualXMaximum);

                if (xSpacing > 0)
                {
                    IDataType<double, double> minValue = this.Chart.Series[seriesIndex].ActualXMinimum;
                    IDataType<double, double> maxValue = this.Chart.Series[seriesIndex].ActualXMaximum;

                    ret.Add(new ValueAxisData<double, double>(minValue, maxValue));
                }
                else
                {
                    ret.Add(null);
                }

                this.spacings.Add(xSpacing);
            }

            return ret;
        }

        private double CalculateAxisSpacing(IDataType<double, double> minValue, IDataType<double, double> maxValue)
        {
            if (this.Chart.ChartCanvas.ActualHeight > 0)
            {
                if (minValue != null && maxValue != null)
                {
                    Typeface typeface = new Typeface(this.Chart.ChartCanvas.FontFamily, this.Chart.ChartCanvas.FontStyle, this.Chart.ChartCanvas.FontWeight, this.Chart.ChartCanvas.FontStretch);

                    // Find the width of a typical label.
                    FormattedText label = new FormattedText(FormatItem(this.Chart.Series[0].LargestDisplayXValue), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, this.Chart.ChartCanvas.FontSize, this.Brush);

                    double minSpacing = label.Width * 1.5;

                    // Now work out the maximum number of labels that we have space for.
                    int numberOfLabels = (int)Math.Floor(this.Chart.ChartCanvas.ActualHeight / minSpacing) + 2;

                    return CalculateAxisSpacing(minValue, maxValue, numberOfLabels);
                }
            }

            return -1;
        }

        private double CalculateAxisSpacing(IDataType<double, double> minValue, IDataType<double, double> maxValue, int maxNumberOfLabels)
        {
            double range = maxValue.Value - minValue.Value;

            if (range > 0)
            {
                double minRangePerLabel = range / maxNumberOfLabels;

                // Now find the magnitude of the range per label.
                int log10 = (int)Math.Floor(Math.Log10(minRangePerLabel));

                // Now calculate a nice round value for the label spacing.
                double labelSpacing = Math.Pow(10, log10);

                if (labelSpacing * 2.0 >= minRangePerLabel)
                {
                    labelSpacing = labelSpacing * 2.0;
                }
                else if (labelSpacing * 2.5 >= minRangePerLabel)
                {
                    labelSpacing = labelSpacing * 2.5;
                }
                else if (labelSpacing * 5.0 >= minRangePerLabel)
                {
                    labelSpacing = labelSpacing * 5.0;
                }
                else
                {
                    labelSpacing = labelSpacing * 10.0;
                }

                return labelSpacing;
            }

            return -1;
        }

        private string FormatItem(IDataType<double, double> value)
        {
            return string.Format("{0:0.00}", value.Value);
        }
    }
}
